import os.path
import gtk
import pango
import traceback
import weakref


if __name__ == "__main__":
    import sys
    import plugins.foolish; reload(plugins.foolish)
    import codebase; reload(codebase)
    import pattern.crossref; reload(pattern.crossref)
    import pattern.timer; reload(pattern.timer)
    import pattern.geometry; reload(pattern.geometry)
    import pattern.circuit; reload(pattern.circuit)
    import algonquin.layout; reload(algonquin.layout)
    import algonquin.circuit; reload(algonquin.circuit)


from pattern.crossref import CrossReference
from algonquin.layout import Layout
from algonquin.circuit import Circuit



#
# Layout Window Class
#
class LayoutWindow(Layout, Circuit):

    ROOT = os.getcwd()
    SESSION_FILENAME = os.path.join(ROOT, "session.txt")
    DATA_DIR = os.path.join(ROOT, "data")

    class State(object):

        DEFAULT_LAYOUT_FILENAME = r"/tmp/saved.dat"
        DEFAULT_STENCIL_FILENAME = r"/tmp/stencils.dat"

        def __init__(self, layout_filename = DEFAULT_LAYOUT_FILENAME,
                           stencil_filenames = [DEFAULT_STENCIL_FILENAME]):
            self.layout_filename = layout_filename
            self.stencil_filenames = stencil_filenames


    class Gesture(object):
        pass

    class LinesGesture(Gesture):
        def __init__(self, points):
            self.points = points
            self.geom = pattern.geometry.LineGeometry()
        def draw(self, window, gc):
            window.draw_lines(gc, self.points)
        def bound(self):
            x_min = min(x for x,y in self.points)
            x_max = max(x for x,y in self.points)
            y_min = min(y for x,y in self.points)
            y_max = max(y for x,y in self.points)
            return Layout.Rectangle(x_min, y_min,
                                    x_max - x_min, y_max - y_min)

        def is_downward_right(self):
            (startx,starty), (endx,endy) = self.points[0], self.points[-1]
            return (endx >= startx) and (endy >= starty)
        
        def curve_amount(self):
            if len(self.points) < 3:
                return 0
            else:
                start, end = self.points[0], self.points[-1]
                return max(self.geom.abscurve(start, p, end) for p in self.points)

    class BoxGesture(Gesture, Layout.Rectangle):
        def __init__(self, left, top, right, bottom):
            Layout.Rectangle.__init__(self, left, top, right-left, bottom-top)

    #
    # Toolbox
    #

    class Tool(object):
        def button_press_cb(self, window, area, event): pass
        def button_release_cb(self, window, area, event): pass
        def motion_nofity_cb(self, window, area, event): pass

    
    class SelectTool(Tool):

        def button_press_cb(self, window, area, event):
            x = int(event.x)
            y = int(event.y)
            self.gesture = window.LinesGesture([(x,y)])
            self.gesture.start_snap = self._snap(window, (x,y))
            clicked = window.hittest(x, y)
            previous = window.notifications.get('selection', [])
            if clicked:
                window.emit(clicked, "selected")
                shade = window.ShadeNotification(clicked)
                if clicked not in [x.shape for x in window.notifications.get('selection', [])]:
                    window.notify('selection', shade, clear=(event.state & gtk.gdk.SHIFT_MASK == 0))
                if event.button == 3:
                    window.popup.popup(None, None, None, event.button, event.time)
                if event.button == 2:
                    verbs = self.verbs(clicked)
                    window._mkverbs(verbs).popup(None, None, None, event.button, event.time)
            else:
                window.notifications['selection'] = []
            window.showProperties(clicked)
            for shape in window.notifications['selection'] + previous:
                rect = shape.bound()
                area.queue_draw_area(rect.left, rect.top, rect.width, rect.height)

        def motion_notify_cb(self, window, area, event):
            x, y = int(event.x), int(event.y)
            self.gesture.points.append((x,y))
            #window.notify('gesture', self.gesture, clear=True)
            if self.gesture.is_downward_right() and self.gesture.curve_amount() > 10:
                box = window.BoxGesture(*(self.gesture.points[0] + self.gesture.points[-1]))
                window.notify('gesture', box, clear=True)
            else:
                line = window.LinesGesture([self.gesture.points[0],
                                            self._snap(window, (x,y))])
                window.notify('gesture', line, clear=True)

        @staticmethod
        def _snap(window, (x,y)):
            snap = window.ConnectorTool.snap_connection_point(window, (x,y))
            if snap is None:
                return (x,y)
            else:
                return snap.at()

        def verbs(self, shape):
            import codebase
            component = codebase.getComponent(shape)
            if hasattr(component, 'VERBS'):
                return component.VERBS
            else:
                return []

        def verb(window, shape, verb):
            window.emit(shape, "verb", verb=verb)

        verb = staticmethod(verb)

    class MoveTool(SelectTool):

        def button_press_cb(self, window, area, event):
            LayoutWindow.SelectTool.button_press_cb(self, window, area, event)
            self.knob = int(event.x), int(event.y)

        def motion_notify_cb(self, window, area, event):
            x, y = int(event.x), int(event.y)
            for n in window.notifications['selection']:
                if hasattr(n.shape, 'moverel'):
                    n.shape.moverel(x - self.knob[0], y - self.knob[1])
            self.knob = x, y
            area.queue_draw()

    class EraserTool(Tool):

        def button_press_cb(self, window, area, event):
            x = int(event.x)
            y = int(event.y)
            clicked = window.hittest(x, y)
            if clicked:
                self.erase(window, clicked)

        @classmethod
        def collect_recursive(cls, shape, s):
            s.add(shape)
            for x in shape.xref: cls.collect_recursive(x(), s)
            return s

        @classmethod
        def remove_recursive(cls, window, shape):
            for xshape in cls.collect_recursive(shape, set()):
                try:
                    window.shapes.remove( xshape )
                except ValueError:
                    pass

        @classmethod
        def erase(cls, window, shape):
            import gc
            cls.remove_recursive(window, shape)
            window.notifications['selection'] = []
            window.showProperties(None)
            gc.collect()
            window.area.queue_draw()

    class BlockTool(Tool):

        def getOrigin(self, window):
            if 'selection' in window.notifications:
                sel = [n.shape for n in window.notifications['selection']]
                if len(sel) == 1 and isinstance(sel[0], Layout.Point):
                    return sel[0]
            return None

        def button_press_cb(self, window, area, event):
            x = int(event.x)
            y = int(event.y)
            origin = self.getOrigin(window)
            if origin is None:
                rect = LayoutWindow.Rectangle(x, y, 20, 20)
            else:
                rect = LayoutWindow.Rectangle(x, y, 20, 20, origin)
            window.add([rect])

        def reoriginate(window, shapes):
            points = [s for s in shapes if isinstance(s, Layout.Point)]
            if len(points) == 1:
                origin = points[0]
            elif len(points) == 0:
                origin = None
            else:
                raise ValueError, "must have at most 1 point in selection"
            for shape in shapes:
                if isinstance(shape, Layout.PlaceableShape):
                    shape.setOrigin(origin)

        reoriginate = staticmethod(reoriginate)


    class ConnectionPointTool(Tool):

        def button_press_cb(self, window, area, event):
            x = int(event.x)
            y = int(event.y)
            clicked = window.hittest(x, y)
            if clicked and hasattr(clicked, "centre"):
                point = self.attach(clicked)
                redraw = point.bound()
                window.add([point])
                window.showProperties(point)


    class CentreConnectionPointTool(ConnectionPointTool):
        """Use this tool to set the centre of a shape as a connection point."""

        def attach(self, clicked):
            att = LayoutWindow.CentreAttachment()
            return LayoutWindow.ConnectionPoint(clicked, att)


    class ConnectorTool(Tool):
        """Use this tool to connect two connection points using a connector
        line."""
        
        def __init__(self):
            self.gesture = None

        def button_press_cb(self, window, area, event):
            x = int(event.x)
            y = int(event.y)
            clicked = self.snap_connection_point(window, (x,y))
            if clicked is not None:
                self.gesture = window.LinesGesture([(x,y),(x,y)])
                self.gesture.start = clicked
                self.gesture.end = None

        def motion_notify_cb(self, window, area, event):
            if self.gesture is None: return
            x = int(event.x)
            y = int(event.y)
            self.gesture.end = self.snap_connection_point(window, (x,y))
            if self.gesture.end is None:
                self.gesture.points[-1] = (x,y)
            else:
                self.gesture.points[-1] = self.gesture.end.at()
            window.notify('gesture', self.gesture, clear=True)

        def button_release_cb(self, window, area, event):
            if self.gesture is None: return
            window.denotify('gesture')
            if self.gesture.end is not None:
                window.add([LayoutWindow.Connector(self.gesture.start,
                                                   self.gesture.end)])
            self.gesture = None

        @staticmethod
        def snap_connection_point(window, (x,y)):
            return window.hittest(x, y, lambda x: isinstance(x, window.ConnectionPoint))

    class LabelTool(Tool):
        """Use this tool to attach captions to existing connection points."""

        def button_press_cb(self, window, area, event):
            x = int(event.x) ; y = int(event.y)
            clicked = window.hittest(x, y, lambda x: isinstance(x, LayoutWindow.ConnectionPoint))
            if clicked is not None:
                att = LayoutWindow.CentreAttachment()
                label = LayoutWindow.Label(clicked, "kaboom", att)
                window.add([label])
                window.showProperties(label)

    class CodeTool(Tool):
        """Use this tool to edit the code of the class associated with
        a block."""
        
        def button_press_cb(self, window, area, event):
            x = int(event.x)
            y = int(event.y)
            clicked = window.hittest(x, y)
            if clicked is not None:
                self.edit(window, clicked)

        def edit(window, shape):
            """Edits the given shape using the code base"""
            import codebase
            session = codebase.EditSession()
            session.connect("edited", lambda s: window.restart(shape))
            codebase.editComponent(shape, session)

        edit = staticmethod(edit)

    class ReuseTool(Tool):
        """Use this tool to associate an existing code fragment with a
        block."""
        
        def button_press_cb(self, window, area, event):
            x = int(event.x)
            y = int(event.y)
            clicked = window.hittest(x, y)
            if clicked is not None:
                import codebase
                dialog = gtk.FileChooserDialog(buttons = (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
                      gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT))
                dialog.set_current_folder(codebase.CodeBase.TMPDIR)
                response = dialog.run()
                if response == gtk.RESPONSE_ACCEPT:
                    clicked.code = codebase.fullToRelativePath(dialog.get_filename())
                    window.restart(clicked)
                dialog.destroy()

    class InspectTool(Tool):
        """Used for debugging: prints out various information about the
        selected object."""
        
        def button_press_cb(self, window, area, event):
            x = int(event.x)
            y = int(event.y)
            clicked = window.hittest(x, y)
            if clicked is not None:
                print clicked
                for e in clicked.xref:
                    print "->", e()
                    for x in e().xref:
                        print "   ->", x()
                for o in clicked.__dict__:
                    a = clicked.__dict__[o]
                    if isinstance(a, CrossReference):
                        print "<-", o, a.referencee
                window.showProperties(clicked)

    class CloneTool(BlockTool):
        """Used to create copies of a control, including connection points
        and existing connections."""

        def button_press_cb(self, window, area, event):
            x = int(event.x)
            y = int(event.y)
            if event.state & gtk.gdk.CONTROL_MASK:
                clicked = window.hittest(x, y)
                if clicked is not None:
                    self.makeStencil(window, clicked)
            else:
                model, sel = window.stencil_pane.get_selection().get_selected()
                origin = self.getOrigin(window)
                if sel is not None:
                    stencil = model.get_value(sel, 1)
                    window.add(stencil.reform((x, y), origin))

        def makeStencil(window, shape):
            stencil = LayoutWindow.Stencil(shape)
            window.stencils.append(["New Stencil", stencil])

        makeStencil = staticmethod(makeStencil)

    class ExpressionTool(Tool):
        """Used to execute arbitrary user code on objects."""

        def evaluate(window, shape):
            expression = LayoutWindow.ExpressionTool.userInput(window.window)
            if expression is not None:
                import codebase
                component = codebase.getComponent(shape)
                exec expression in {'o': shape, 'c': component}

        evaluate = staticmethod(evaluate)

        def userInput(window, title = "Expression Tool"):
            d = gtk.Dialog(title = title, parent = window,
                           buttons = (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
                                      gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT))
            entry = gtk.Entry()
            d.vbox.add(entry)
            entry.show()
            response = d.run()
            d.destroy()
            if response == gtk.RESPONSE_ACCEPT:
                return entry.get_text()
            else:
                return None

        userInput = staticmethod(userInput)


    class Stencil(object):

        def __init__(self, root):
            shapes = []
            self.recursively_append(shapes, root)
            import pickle
            self.template = pickle.dumps(shapes)

        def reform(self, topleft, origin = None):
            import pickle
            freshcopy = pickle.loads(self.template)
            if origin is not None:
                freshcopy[0].setOrigin(origin)
            freshcopy[0].moveto(topleft[0], topleft[1])
            return freshcopy

        def recursively_append(self, lst, shape):
            lst.append(shape)
            for xshape in shape.xref:
                self.recursively_append(lst, xshape())


        
    #
    # Initialization phase
    #
    
    def __init__(self, session_filename = SESSION_FILENAME):
        self.session = self.restoreSessionFromFile(session_filename)
        self.session.filename = session_filename
        self.tool = LayoutWindow.SelectTool()
        self.notifications = {}
        # Build GUI
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.set_title("Layout Window")
        self.window.connect("destroy", self.save_and_quit)
        self.vbox = gtk.VBox()
        self.createToolbar()
        self.hbox = gtk.HBox()
        self.createStencilPane()
        for stencil_filename in self.session.stencil_filenames:
            self.restoreStencilsFromFile(stencil_filename)
        self.createDrawingArea()
        self.createPropertyPane()
        self.createPopupMenus()
        self.loadIcons()
        self.vbox.pack_start(self.hbox, True, True, 10)
        self.window.add(self.vbox)
        Layout.lw = self
        self.hbox.show()
        self.vbox.show()
        self.window.show()
        self.restoreShapesFromFile(self.session.layout_filename)

    def restoreSessionFromFile(self, filename):
        import os.path
        if os.path.isfile(filename):
            session_file = open(filename)
            layout = session_file.readline().strip()
            stencils = [x.strip() for x in session_file.readlines()]
            session_file.close()
            return LayoutWindow.State(layout, stencils)
        else:
            return LayoutWindow.State()

    def restoreShapesFromFile(self, filename):
        import os.path, pickle
        if os.path.isfile(filename):
            self.shapes = pickle.load(open(filename))
            self.populate(self.shapes)
        else:
            self.shapes = []

    def restoreStencilsFromFile(self, filename):
        import os.path, pickle
        self.stencils.session = filename
        list_store = gtk.ListStore(str, object)
        if os.path.isfile(filename):
            stored = pickle.load(open(filename))
            for row in stored:
                list_store.append(row)
        entry = self.stencil_sets.append([filename, list_store])
        self.stencil_combo.set_active_iter(entry)

    def createToolbar(self):
        self.toolbar = gtk.Toolbar()
        self.toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL)
        self.toolbar.set_style(gtk.TOOLBAR_BOTH)
        self.toolbar.set_border_width(5)
        # Add tool selection button group
        tool = None
        for label, tc in [("Select", LayoutWindow.SelectTool), \
                          ("Move", LayoutWindow.MoveTool), \
                          ("Block", LayoutWindow.BlockTool), \
                          ("Erase", LayoutWindow.EraserTool), \
                          ("C. Point", LayoutWindow.CentreConnectionPointTool), \
                          ("Connect", self.ConnectorTool), \
                          ("Label", self.LabelTool), \
                          ("Code", self.CodeTool), \
                          ("Reuse", self.ReuseTool), \
                          ("Inspect", self.InspectTool),
                          ("Clone", self.CloneTool)]:
            tool = self._tool(label, lambda button, tc=tc:
                         self.toolbar_cb(button, tc), group=tool)
        self._toolspace()
        # Add new layout button
        self._toolbutton("New...", self.new_session)
        # Add open button
        self._toolbutton("Open...", self.open_file)
        # Add save button
        self._toolbutton("Save", self.save_session)
        # Show toolbar
        self.vbox.pack_start(self.toolbar, False, False, 5)
        self.toolbar.show()

    def _tool(self, label, action, group):
        if hasattr(gtk, 'RadioToolButton'):
            tool = gtk.RadioToolButton(group = group)
            tool.set_label(label)
            tool.set_active(0)
            tool.show()
            tool.connect("clicked", action)
            self.toolbar.insert(tool, -1)
            return tool
        else:
            self.toolbar.append_item(label, None, "", None, action)

    def _toolspace(self):
        if hasattr(gtk, 'SeparatorToolItem'):
            sep = gtk.SeparatorToolItem(); sep.show()
            self.toolbar.insert(sep, -1)
        else:
            self.toolbar.append_space()

    def _toolbutton(self, label, action):
        if hasattr(gtk, 'ToolButton'):
            button = gtk.ToolButton()
            button.set_label(label); button.show()
            button.connect("clicked", action)
            self.toolbar.insert(button, -1)
        else:
            self.toolbar.append_item(label, "", "", None, action)

    def createDrawingArea(self):
        self.area = gtk.DrawingArea()
        self.area.set_size_request(4000, 3000)
        self.area.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("navajowhite"))
        self.configureEventsForDrawingArea()
        self.sw = gtk.ScrolledWindow()
        self.sw.set_size_request(650, 500)
        self.sw.add_with_viewport(self.area)
        self.area.show()
        self.sw.show()
        self.hbox.pack_start(self.sw, True, True, 2)

    def createPropertyPane(self):
        self.properties = gtk.ListStore(str, str)
        self.properties.showing = None
        self.property_pane = gtk.TreeView(self.properties)
        value = gtk.CellRendererText()
        value.set_property("editable", True)
        value.connect('edited', self.property_edited_cb)
        column1 = gtk.TreeViewColumn("Name", gtk.CellRendererText(), text = 0)
        column2 = gtk.TreeViewColumn("Value", value, text = 1)
        self.property_pane.append_column(column1)
        self.property_pane.append_column(column2)
        self.property_pane.show()
        sw = gtk.ScrolledWindow()
        sw.set_size_request(140, 120)
        sw.add_with_viewport(self.property_pane)
        sw.show()
        self.hbox.pack_start(sw, False, False, 2)

    def createStencilPane(self):
        self.stencils = gtk.ListStore(str, object)
        self.stencil_pane = gtk.TreeView(self.stencils)
        value = gtk.CellRendererText()
        value.set_property("editable", True)
        value.connect('edited', self.stencil_name_edited_cb)
        column1 = gtk.TreeViewColumn("Stencil", value, text = 0)
        self.stencil_pane.append_column(column1)
        self.stencil_pane.set_size_request(100, 100)
        self.stencil_pane.connect("button_press_event", self.stencil_button_press_cb)
        self.stencil_pane.showing = None
        self.stencil_pane.show()
        self.stencil_sets = gtk.ListStore(str, object)
        self.stencil_sets.append(["New...", None])
        self.stencil_combo = gtk.ComboBox(self.stencil_sets)
        cell = gtk.CellRendererText()
        self.stencil_combo.pack_start(cell)
        self.stencil_combo.add_attribute(cell, 'text', 0)
        self.stencil_combo.connect("changed", self.stencil_combo_changed_cb)
        self.stencil_combo.show()
        vbox = gtk.VBox()
        vbox.pack_start(self.stencil_combo, False, False, 0)
        vbox.pack_start(self.stencil_pane, True, True, 0)
        vbox.show()
        self.hbox.pack_start(vbox, False, False, 2)

    def createPopupMenus(self):
        self.popup = gtk.Menu()
        self._popupItem(self.popup, "Restart", LayoutWindow.restart)
        self._popupItem(self.popup, "Edit Code", self.CodeTool.edit)
        self._popupItem(self.popup, "Delete", self.EraserTool.erase)
        self._popupItem(self.popup, "Make Stencil", self.CloneTool.makeStencil)
        self._popupItem(self.popup, "Set Origin", self.BlockTool.reoriginate, multi=True)
        self._popupItem(self.popup, "Evaluate...", self.ExpressionTool.evaluate)
        self.stencil_popup = gtk.Menu()
        item_remove = gtk.MenuItem("Remove Stencil")
        item_remove.connect("activate", self.stencil_menu_remove_cb)
        self.stencil_popup.append(item_remove)
        item_remove.show()

    def _popupItem(self, popup, label, selection_functor, args=(), multi=False):
        item = gtk.MenuItem(label)
        if multi:
            item.connect("activate",
                         lambda x, func=selection_functor, args=args:
                              self.applyToSelections(selection_functor, *args))
        else:
            item.connect("activate",
                         lambda x, func=selection_functor, args=args:
                              self.applyToSelection(selection_functor, *args))
        popup.append(item)
        item.show()
        return item

    def _mkverbs(self, verbs):
        verbs_submenu = gtk.Menu()
        for verb in verbs:
            self._popupItem(verbs_submenu, verb, self.SelectTool.verb, (verb,))
        return verbs_submenu

    def loadIcons(self):
        if hasattr(gtk, 'IconTheme'):
            self.icons = gtk.IconTheme()
            self.icons.append_search_path(self.DATA_DIR)

    def configureEventsForDrawingArea(self):
        self.area.connect("expose-event", self.area_expose_cb)
        self.area.connect("button_press_event", self.area_button_press_cb)
        self.area.connect("button_release_event", self.area_button_release_cb)
        self.area.connect("motion_notify_event", self.area_motion_notify_cb)
        self.area.set_events(gtk.gdk.EXPOSURE_MASK
                             | gtk.gdk.BUTTON_PRESS_MASK
                             | gtk.gdk.BUTTON_RELEASE_MASK
                             | gtk.gdk.POINTER_MOTION_MASK)
    #
    # Editing & Management
    #

    def hittest(self, x, y, filtering = lambda x: True):
        best = None
        best_size = 1 << 20
        for shape in self.shapes:
            if filtering(shape) and hasattr(shape, 'hittest'):
                try:
                    area = shape.bound().area()
                    if shape.hittest(x, y) and area < best_size:
                        best = shape
                        best_size = area
                except:
                    traceback.print_exc()

        return best

    def notify(self, category, notification, clear=False):
        if clear: self.denotify(category)
        if notification is None: self.denotify(category); return
        if self.notifications.has_key(category):
            self.notifications[category].append(notification)
        else:
            self.notifications[category] = [notification]
        self.queue_draw_rect(notification.bound())
        #self.area.queue_draw()

    def denotify(self, category):
        if self.notifications.has_key(category):
            del self.notifications[category]
            self.area.queue_draw()

    def manipulate(self, shape, manip):
        manip(shape)
        if hasattr(self, 'area'):
            self.area.queue_draw()
            if shape is self.properties.showing:
                self.showProperties(shape)

    def showProperties(self, shape):
        self.properties.clear()
        self.properties.showing = shape
        if shape is not None:
            self.properties.append(["type", type(shape).__name__])
            for attr in shape.properties():
                if attr != "xref":
                    self.properties.append([attr, str(getattr(shape, attr))])

    def applyToSelection(self, operation, *args, **kw):
        sel = self.notifications['selection']
        if len(sel) == 1:
            operation(self, sel[0].shape, *args, **kw)

    def applyToSelections(self, operation, *args, **kw):
        sel = self.notifications['selection']
        operation(self, [s.shape for s in sel], *args, **kw)

    def add(self, shapes):
        self.shapes.extend(shapes)
        self.populate(shapes)
        self.area.queue_draw()

    def queue_draw_rect(self, rectangle):
        self.area.queue_draw_area(rectangle.left, rectangle.top, \
                                  rectangle.width, rectangle.height)

    def choose_stencil_set(self, stencil_set):
        self.stencils = stencil_set
        self.stencil_pane.set_model(self.stencils)
        
    #
    # Event callbacks
    #

    def area_expose_cb(self, area, event):
        self.style = self.area.get_style()
        fg_gc = self.style.fg_gc[gtk.STATE_NORMAL]
        lt_gc = self.style.light_gc[gtk.STATE_NORMAL]

        #import os.path
        #xpm = os.path.join(self.DATA_DIR, "gtk.xpm")
        #pixmap, mask = gtk.gdk.pixmap_create_from_xpm(
        #    self.area.window, self.style.bg[gtk.STATE_NORMAL], xpm)
        #self.area.window.draw_drawable(fg_gc, pixmap, 0, 0, 115, 125,
        #                               -1, -1)

        exposure = Layout.Rectangle(event.area.x, event.area.y,
                                    event.area.width, event.area.height)
  
        bad = []
        for nature, members in self.notifications.items():
            for shape in members:
                shape.draw(self.area.window, lt_gc)
        for shape in self.shapes:
            try:
                if shape.bound().intersects(exposure):
                    shape.draw(self.area.window, fg_gc)
            except:
                bad.append(shape)
                traceback.print_exc()

        for b in bad: self.shapes.remove(b)

    def area_button_press_cb(self, area, event):
        if self.tool:
            self.tool.button_press_cb(self, area, event)

    def area_button_release_cb(self, area, event):
        if self.tool:
            self.tool.button_release_cb(self, area, event)

    def area_motion_notify_cb(self, area, event):
        if not event.is_hint and event.state & gtk.gdk.BUTTON1_MASK:
            if self.tool and hasattr(self.tool, "motion_notify_cb"):
                self.tool.motion_notify_cb(self, area, event)

    def stencil_button_press_cb(self, pane, event):
        if event.button == 3:
            self.stencil_popup.popup(None, None, None, event.button, event.time)

    def stencil_menu_remove_cb(self, item):
        model, sel = self.stencil_pane.get_selection().get_selected()
        if sel is not None:
            model.remove(sel)
        
    def property_edited_cb(self, cell, path, new_text):
        if self.properties.showing:
            subject = self.properties.showing
            attr = self.properties[path][0]
            previous = getattr(subject, attr)
            # Maintain the original type of the attribute
            if isinstance(previous, str):
                newval = new_text
            elif isinstance(previous, int):
                newval = int(new_text)
            elif isinstance(previous, LayoutWindow.Attachment):
                newval = self.attachment_expr(new_text)
            else:
                newval = None
            # Set newval as the new value of the attribute
            if newval is not None:
                self.manipulate(self.properties.showing,
                    lambda x: setattr(x, self.properties[path][0], newval))

    def stencil_name_edited_cb(self, cell, path, new_text):
        self.stencils[path][0] = new_text

    def stencil_combo_changed_cb(self, combobox):
        if combobox.get_active_text() == "New...":
            if not self.new_stencil_set():
                combobox.set_active_iter(self.stencil_pane.showing)
            return
        self.choose_stencil_set(self.stencil_sets.get(combobox.get_active_iter(), 1)[0])
        self.stencil_pane.showing = combobox.get_active_iter()

    def toolbar_cb(self, button, toolclass):
        self.tool = toolclass()

    def bye(self, widget = None):
        self.window.destroy()

    def save_session(self, widget = None):
        import cPickle as pickle
        # Save shapes
        dumped = pickle.dumps(self.shapes)
        open(self.session.layout_filename, "wb").write(dumped)
        # Save stencils
        for filename, stencil_set in self.stencil_sets:
            if stencil_set is not None:
                dumped = pickle.dumps([list(x) for x in stencil_set])
                open(filename, "wb").write(dumped)
        # Save the session
        f = open(self.session.filename, "w")
        print >>f, self.session.layout_filename
        for stencil_filename in self.session.stencil_filenames:
            print >>f, stencil_filename
        f.close()

    def open_file(self, widget = None):        
        dialog = gtk.FileChooserDialog(buttons = (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
              gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT))
        dialog.set_current_folder(codebase.CodeBase.TMPDIR)
        response = dialog.run()
        try:
            if response == gtk.RESPONSE_ACCEPT:
                self.shutdownAll()
                self.session.layout_filename = dialog.get_filename()
                self.restoreShapesFromFile(self.session.layout_filename)
        finally:
            dialog.destroy()

    def new_session(self, widget = None):
        dialog = self._saveas_dialog()
        response = dialog.run()
        try:
            if response == gtk.RESPONSE_ACCEPT:
                self.session.layout_filename = dialog.get_filename()
                self.shapes = []
        finally:
            dialog.destroy()

    def new_stencil_set(self, widget = None):
        dialog = self._saveas_dialog()
        response = dialog.run()
        try:
            if response == gtk.RESPONSE_ACCEPT:
                import codebase
                filename = codebase.fullToRelativePath(dialog.get_filename())
                store = gtk.ListStore(str, object)
                entry = self.stencil_sets.append((filename, store))
                self.stencil_combo.set_active_iter(entry)
                return True
            else:
                return False
        finally:
            dialog.destroy()

    def _saveas_dialog(self):
        dialog = gtk.FileChooserDialog(action=gtk.FILE_CHOOSER_ACTION_SAVE,
                                       buttons=(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
                                                gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT))
        dialog.set_current_folder(codebase.CodeBase.TMPDIR)
        return dialog

    def shutdownAll(self):
        for shape in self.shapes:   self.emit(shape, "shutdown")

    def save_and_quit(self, app):
        import codebase
        try:
            self.shutdownAll()
            codebase.CodeBase.EDITOR.end()
            self.save_session()
        finally:
            gtk.main_quit()
        return False




# Serialization patch
Stencil = LayoutWindow.Stencil
from algonquin.layout import Point, AbsolutePoint, RelativePoint, \
     Rectangle, ConnectionPoint, Connector, FreeFormConnector, Label

from algonquin.layout import CentreAttachment, LeftSideAttachment, \
     RightSideAttachment, TopSideAttachment, BottomSideAttachment


    
if __name__ == "__main__":
    try:
        del lw.shapes
    except:
        pass
    import sys
    lw = LayoutWindow(*sys.argv[1:2])
    gtk.main()
